﻿#region Using Statements
using System;
using System.Diagnostics;
using System.Collections.Generic;
#endregion

namespace DirectZZT.Shared.Scripting
{
    /// <summary>
    /// Class that assists in "debugging" scripts.
    /// </summary>
    public static class ScriptDebugger
    {
        #region Events

        /// <summary>
        /// Raised when <see cref="M:Event(IScript,string[])"/> was called.
        /// </summary>
        public static event EventHandler<ScriptDebuggerEventEventArgs> EventOcurred;
        /// <summary>
        /// Raised when a script was registered.
        /// </summary>
        public static event EventHandler<ScriptDebuggerEventArgs> ScriptRegistered;
        /// <summary>
        /// Raised when a script was unregistered.
        /// </summary>
        public static event EventHandler<ScriptDebuggerEventArgs> ScriptUnregistered;
        /// <summary>
        /// Raised when a new world was loaded and all scripts shall be cleared.
        /// </summary>
        public static event EventHandler ScriptsCleared;
        /// <summary>
        /// Raised when a script had its execution line changed.
        /// </summary>
        public static event EventHandler<ScriptExecutionLineChangedEventArgs> ExecutionLineChanged;

        private static void OnEventOccurred(ScriptDebuggerEventEventArgs e)
        {
            var copy = EventOcurred;
            if (copy != null)
            {
                copy(null, e);
            }
        }

        private static void OnScriptRegistered(ScriptDebuggerEventArgs e)
        {
            var copy = ScriptRegistered;
            if (copy != null)
            {
                copy(null, e);
            }
        }

        private static void OnScriptUnregistered(ScriptDebuggerEventArgs e)
        {
            var copy = ScriptUnregistered;
            if (copy != null)
            {
                copy(null, e);
            }
        }

        private static void OnScriptsCleared()
        {
            var copy = ScriptsCleared;
            if (copy != null)
            {
                copy(null, null);
            }
        }

        private static void OnExecutionLineChanged(ScriptExecutionLineChangedEventArgs e)
        {
            var copy = ExecutionLineChanged;
            if (copy != null)
            {
                copy(null, e);
            }
        }

        #endregion

        #region Fields

        private static List<IScript> _registeredScripts;

        #endregion

        #region Properties

        /// <summary>
        /// Gets/sets whether or not to track execution of scripts.
        /// </summary>
        public static bool TrackExecution { get; set; }
        /// <summary>
        /// Gets/sets whether or not to enable debugger events.
        /// </summary>
        public static bool EnableEvents { get; set; }

        #endregion

        #region Constructors

        static ScriptDebugger()
        {
            _registeredScripts = new List<IScript>();

#if DEBUG
            EnableEvents = true;
#endif
        }

        #endregion

        /// <summary>
        /// Notifies the debugger of an event.
        /// </summary>
        /// <param name="source">The script instance that was the source of the event.</param>
        /// <param name="eventType"></param>
        /// <param name="parameters">The optional parameters.</param>
        public static void NotifyEvent(IScript source, EventType eventType, params string[] parameters)
        {
            if (!EnableEvents) { return; }
            OnEventOccurred(new ScriptDebuggerEventEventArgs()
            {
                Script = source,
                EventType = eventType,
                Parameters = parameters
            });
        }

        /// <summary>
        /// Notifies that a script has moved to a different line in its script code.
        /// </summary>
        /// <param name="source">The affected script.</param>
        /// <param name="line">The line that is executed.</param>
        public static void NotifyExecutionLineChanged(IScript source, int line)
        {
            if (!TrackExecution) { return; }
            OnExecutionLineChanged(new ScriptExecutionLineChangedEventArgs()
            {
                Script = source,
                Line = line,
            });
        }

        /// <summary>
        /// Registers a script at the debugger.
        /// </summary>
        /// <param name="script"></param>
        public static void RegisterScript(IScript script)
        {
            lock (_registeredScripts)
            {
                _registeredScripts.Add(script);
                OnScriptRegistered(new ScriptDebuggerEventArgs() { Script = script });
            }
        }

        /// <summary>
        /// Unregisters a script from the debugger.
        /// </summary>
        /// <param name="script"></param>
        public static void UnregisterScript(IScript script)
        {
            lock (_registeredScripts)
            {
                _registeredScripts.Remove(script);
                OnScriptUnregistered(new ScriptDebuggerEventArgs() { Script = script });
            }
        }

        /// <summary>
        /// Clears all scripts from the debugger.
        /// </summary>
        public static void ClearScripts()
        {
            lock (_registeredScripts)
            {
                _registeredScripts.Clear();
                OnScriptsCleared();
            }
        }

        /// <summary>
        /// Returns all registered scripts.
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<IScript> GetRegisteredScripts()
        {
            lock (_registeredScripts)
            {
                return _registeredScripts;
            }
        }

        #region Nested types

        /// <summary>
        /// Event args for the script debugger.
        /// </summary>
        public class ScriptExecutionLineChangedEventArgs : ScriptDebuggerEventArgs
        {
            /// <summary>
            /// Gets/sets the line that is executed.
            /// </summary>
            public int Line { get; set; }
        }

        /// <summary>
        /// Event args for the script debugger.
        /// </summary>
        public class ScriptDebuggerEventEventArgs : ScriptDebuggerEventArgs
        {
            /// <summary>
            /// The optional parameters.
            /// </summary>
            public string[] Parameters { get; set; }
            /// <summary>
            /// Gets/sets the event type.
            /// </summary>
            public EventType EventType { get; set; }
        }

        /// <summary>
        /// The script debugger base event.
        /// </summary>
        public class ScriptDebuggerEventArgs : EventArgs
        {
            /// <summary>
            /// The script instance.
            /// </summary>
            public IScript Script { get; set; }
        }

        /// <summary>
        /// Event type.
        /// </summary>
        public enum EventType
        {
            /// <summary>
            /// Something rather unimportant occurred.
            /// </summary>
            Trace = 0,
            /// <summary>
            /// Something informatical occurred.
            /// </summary>
            Info = 1,
            /// <summary>
            /// A warning occurred.
            /// </summary>
            Warning = 2,
            /// <summary>
            /// A fault occurred.
            /// </summary>
            Error = 3,
            /// <summary>
            /// A fault occurred.
            /// </summary>
            Fault = 4,
            /// <summary>
            /// A debug event occurred.
            /// </summary>
            Debug = 5,
        }

        #endregion
    }
}
